﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Dynamic;
using Platform.Infrastructure;
using Platform.ObjectModel.Design;
using System.Reflection;
using FastMember;

namespace Platform.ObjectModel
{


	public abstract class DataObjectFlake_v1 : DynamicObject
	{


		/// <summary>
		/// Obtém ou define os valores dinâmicos da entidade
		/// </summary>
		internal IDictionary<string, object> Values
		{
			get;
			private set;
		}


		public object Get( DataObjectName qualifiedName )
		{
			Arguments.IsNotNull( "qualifiedName", qualifiedName );
			return this.Get( qualifiedName.Name );
		}

		public object Get( string propertyName )
		{
			object value;

			if ( this.Values.TryGetValue( propertyName, out value ) )
				return value;
			else
				return null;
		}



		public override bool TrySetMember( SetMemberBinder binder, object value )
		{
			return base.TrySetMember( binder, value );
		}

		public override bool TryGetMember( GetMemberBinder binder, out object result )
		{
			return base.TryGetMember( binder, out result );
		}
	}




	/////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////
	//		Antigo
	/////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////


	/**
	 * Exemplo de como é feita a chamada para um método dinâmico no DLR
	 * http://stackoverflow.com/questions/2079870/dynamically-adding-members-to-a-dynamic-object
	 */
	/// <summary>
	/// Representa um conjunto de dados que é mantido pelo sistema
	/// </summary>
	[Obsolete("Reve necessidade desta classe. Finalizar os métodos de acesso aos dados dinâmicamente.")]
	public abstract class DataObjectFlake : IDynamicMetaObjectProvider
	{
		// TODO: Rever a forma de como está sendo acessada as propriedades, trazer a lógica pra dentro desse objeto, não há necessidade de ter referência para "ObjectAcessor"
		private Lazy<ObjectAccessor> accessor;

		protected DataObjectFlake()
		{
			this.Values = new Dictionary<string, object>( StringComparer.InvariantCultureIgnoreCase );
			this.accessor = new Lazy<ObjectAccessor>( () => ObjectAccessor.Create( this ), false );
		}

		/// <summary>
		/// Obtém ou define os valores dinâmicos da entidade
		/// </summary>
		internal IDictionary<string, object> Values
		{
			get;
			private set;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fieldName"></param>
		/// <returns></returns>
		public object this[ string fieldName ]
		{
			get { return this.accessor.Value[ fieldName ]; }
			set { this.accessor.Value[ fieldName ] = value; }
		}


		public object Get( DataObjectName qualifiedName )
		{
			Arguments.IsNotNull( "qualifiedName", qualifiedName );
			return this.Get( qualifiedName.Name );
		}

		public object Get( string propertyName )
		{
			object value;

			if ( this.Values.TryGetValue( propertyName, out value ) )
				return value;
			else
				return null;
		}

		//public void Set( DataObjectName qualifiedName, object value )
		//{
		//    Arguments.IsNotNull( "qualifiedName", qualifiedName );
		//    this.Set( qualifiedName.Name, value );
		//}

		public void Set( string propertyName, object value )
		{
			this.Values[ propertyName ] = value;
		}
		
		#region IDynamicMetaObjectProvider Members

		public DynamicMetaObject GetMetaObject( Expression parameter )
		{
			return new DataObjectMetadata( parameter, this );
		}

		#endregion

		#region [ class DataObjectMetadata ]

		/// <summary>
		/// Objeto que define a estrutura do <see cref="DataObjectFlake"/> para trabalhar com linguagens dinâmicas
		/// </summary>
		internal sealed class DataObjectMetadata : DynamicMetaObject
		{
			public DataObjectMetadata( Expression parameter, DataObjectFlake value )
				//	: base( parameter, BindingRestrictions.GetInstanceRestriction( parameter, value ), value )
				: base( parameter, BindingRestrictions.Empty, value )
			{ }

			public override DynamicMetaObject BindGetMember( GetMemberBinder binder )
			{
				//var property = this.LimitType.GetProperty( binder.Name, binder.ReturnType );
				PropertyInfo property;
				bool isObjReturnType = binder.ReturnType == typeof( object );

				if ( isObjReturnType )
					property = this.LimitType.GetProperty( binder.Name );
				else
					property = this.LimitType.GetProperty( binder.Name, binder.ReturnType );

				// Cria a expressão para acessar a propriedade
				if ( property != null )
				{
					Expression getProperty =
						Expression.MakeMemberAccess(
							Expression.Convert( this.Expression, this.LimitType ),
							property );

					if ( isObjReturnType )
						getProperty = Expression.Convert( getProperty, typeof( object ) );

					return new DynamicMetaObject( getProperty, BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
				else
				{
					var methodCall =
						Expression.Call(
							Expression.Convert( this.Expression, this.LimitType ),
							this.LimitType.GetMethod( "Get" ),
							Expression.Constant( binder.Name ) );

					return new DynamicMetaObject( methodCall, BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
			}

			public override DynamicMetaObject BindSetMember( SetMemberBinder binder, DynamicMetaObject value )
			{
				//var property = this.LimitType.GetProperty( binder.Name, binder.ReturnType );
				var property = this.LimitType.GetProperties().FirstOrDefault( prop => String.Equals( prop.Name, binder.Name, StringComparison.InvariantCultureIgnoreCase ) );
								
				if ( property != null )
				{
					Expression valueExp = value.Expression;
					if ( value.Expression.Type != property.PropertyType )
					    valueExp = Expression.Convert( value.Expression, property.PropertyType );

					var setProperty = Expression.Assign(
							Expression.MakeMemberAccess(
								Expression.Convert( this.Expression, this.LimitType )
								, property ),
								valueExp );

					return new DynamicMetaObject( setProperty.Void(), BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
					//return new DynamicMetaObject( setProperty.Void(), BindingRestrictions.GetTypeRestriction( value.Expression, property.PropertyType ) );
				}
				else
				{
					var methodCall =
						Expression.Call(
							Expression.Convert( this.Expression, this.LimitType ),
							this.LimitType.GetMethod( "Set" ),
							Expression.Constant( binder.Name ),
							value.Expression );

					return new DynamicMetaObject( methodCall.Void(), BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
			}

			public override IEnumerable<string> GetDynamicMemberNames()
			{
				return base.GetDynamicMemberNames();
			}
		}

		#endregion
	}


	public sealed class DataObjectValues : Dictionary<string, object>
	{

		//public DataObjectValues()
		//    : base( StringComparer.InvariantCultureIgnoreCase )
		//{ }

		//public DataObjectValues( int capacity )
		//    : base( capacity, StringComparer.InvariantCultureIgnoreCase )
		//{ }


		internal DataObjectValues( DataObjectFlake dataObject )
			: base( StringComparer.InvariantCultureIgnoreCase )
		{
			this.DataObject = dataObject;
		}


		public DataObjectFlake DataObject
		{ get; private set; }


		//public object Get( string name )
		//{

		//}
	}

	public class DataObjectRawValues : List<Tuple<DataObjectName, byte[]>>
	{

	}

	//public interface IStreamer
	//{
	//    string Serialize( object value );

	//    object Deserialize( string value );
	//}

	#region [ class ArrayBasedDictionary ] 

	public class ArrayBasedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
	{
		private List<KeyValuePair<TKey, TValue>> pairs;


		public ArrayBasedDictionary()
		{
			this.pairs = new List<KeyValuePair<TKey, TValue>>();
		}

		public int IndexOf( TKey key )
		{
			for ( int index = 0; index < this.pairs.Count; index++ )
				if ( Object.Equals( this.pairs[ index ].Key, key ) )
					return index;
			return -1;
		}

		public TValue this[ int index ]
		{
			get { return this.pairs[ index ].Value; }
			set
			{
				var old = this.pairs[ index ];
				this.pairs[ index ] = new KeyValuePair<TKey, TValue>( old.Key, value );
			}
		}

		#region IDictionary<TKey,TValue> Members

		public void Add( TKey key, TValue value )
		{
			this.Add( new KeyValuePair<TKey, TValue>( key, value ) );
		}

		public bool ContainsKey( TKey key )
		{
			return this.pairs.Any( p => Object.Equals( p.Key, key ) );
		}

		public ICollection<TKey> Keys
		{
			get { return this.pairs.Select( p => p.Key ).ToArray(); }
		}

		public bool Remove( TKey key )
		{
			int index = this.IndexOf( key );
			if ( index >= 0 )
			{
				this.pairs.RemoveAt( index );
				return true;
			}
			else
				return false;
		}

		public bool TryGetValue( TKey key, out TValue value )
		{
			int index = this.IndexOf( key );
			if ( index >= 0 )
			{
				value = this[ index ];
				return true;
			}
			else
			{
				value = default( TValue );
				return false;
			}
		}

		public ICollection<TValue> Values
		{
			get { return this.pairs.Select( p => p.Value ).ToArray(); }
		}

		public TValue this[ TKey key ]
		{
			get
			{
				int index = this.IndexOf( key );
				if ( index < 0 )
					throw new InvalidOperationException();
				return this[ index ];
			}
			set
			{
				int index = this.IndexOf( key );
				if ( index < 0 )
					this.Add( key, value );
				else
					this[ index ] = value;
			}
		}

		#endregion

		#region ICollection<KeyValuePair<TKey,TValue>> Members

		public void Add( KeyValuePair<TKey, TValue> item )
		{
			this.pairs.Add( item );
		}

		public void Clear()
		{
			this.pairs.Clear();
		}

		public bool Contains( KeyValuePair<TKey, TValue> item )
		{
			return this.pairs.Contains( item );
		}

		public void CopyTo( KeyValuePair<TKey, TValue>[] array, int arrayIndex )
		{
			this.pairs.CopyTo( array, arrayIndex );
		}

		public int Count
		{
			get { return this.pairs.Count; }
		}

		public bool IsReadOnly
		{
			get { return false; }
		}

		public bool Remove( KeyValuePair<TKey, TValue> item )
		{
			return this.pairs.Remove( item );
		}

		#endregion

		#region IEnumerable<KeyValuePair<TKey,TValue>> Members

		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
		{
			return this.pairs.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		#endregion
	}

	#endregion

	//public class DataObjectBuilder<TObject>
	//    where TObject : DataObjectFlake, new()
	//{

	//    public DataObjectBuilder( Schema schema )
	//    {
	//        this.Schema = schema;
	//    }

	//    public Schema Schema
	//    {
	//        get;
	//        private set;
	//    }

	//    public void SetFieldValue( DataObjectName name, object value )
	//    {

	//    }
	//}

	public class DataObjectReader
	{

	}



}
